package com.xh3140.xrouter;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

public final class RouteProcessor extends AbstractProcessor {
  private static final String PACKAGE_NAME;
  private static final String CLASS_NAME_MANAGER = "RouterManager";
  private static final String CLASS_NAME_REGISTER = "RouterRegister";
  private static final String METHOD_NAME_REGISTER = "register";
  private static final String METHOD_NAME_REGISTERING = "registering";
  private static final String KEY_MODULE_NAME = "ROUTER_MODULE_NAME";
  private static final Pattern PATTERN_CHECK = Pattern.compile("[_a-zA-Z][-_a-zA-Z0-9]*");
  private static final Pattern PATTERN_REPLACE = Pattern.compile("[-_]+([a-zA-Z])");
  private Filer filer;
  private Messager messager;
  private String moduleName;

  static {
    Package thisPackage = Router.class.getPackage();
    if (thisPackage == null) {
      throw new IllegalStateException("no package!");
    }
    PACKAGE_NAME = thisPackage.getName();
  }

  private static String moduleRegisterName(String moduleName) {
    if (moduleName == null || !PATTERN_CHECK.matcher(moduleName).matches()) {
      throw new IllegalArgumentException("error arg moduleName: " + moduleName);
    }
    Matcher matcher;
    char first = moduleName.charAt(0);
    if (first != '-' && first != '_') {
      matcher = PATTERN_REPLACE.matcher("_" + moduleName);
    } else {
      matcher = PATTERN_REPLACE.matcher(moduleName);
    }
    StringBuffer buffer = new StringBuffer();
    while (matcher.find()) {
      String group = matcher.group(1);
      if (group != null) {
        matcher.appendReplacement(buffer, group.toUpperCase());
      }
    }
    matcher.appendTail(buffer);
    buffer.append(CLASS_NAME_REGISTER);
    return buffer.toString();
  }

  @Override
  public Set<String> getSupportedAnnotationTypes() {
    return Collections.singleton(Router.class.getCanonicalName());
  }

  @Override
  public Set<String> getSupportedOptions() {
    return Collections.singleton(KEY_MODULE_NAME);
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return processingEnv.getSourceVersion();
  }

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    filer = processingEnv.getFiler();
    messager = processingEnv.getMessager();
    String option;
    Map<String, String> options = processingEnv.getOptions();
    if (options == null || ((option = options.get(KEY_MODULE_NAME)) == null) || option.isEmpty()) {
      messager.printMessage(Diagnostic.Kind.ERROR, "router-compiler no option: " + KEY_MODULE_NAME);
    } else {
      moduleName = option;
      messager.printMessage(Diagnostic.Kind.NOTE, "router-compiler option: " + KEY_MODULE_NAME + " = " + option);
    }
  }

  @Override
  public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    // find annotated class.
    Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Router.class);
    if (elements.isEmpty()) {
      return true;
    }
    // create override method from RouterRegister interface.
    MethodSpec.Builder registering = MethodSpec.methodBuilder(METHOD_NAME_REGISTERING)
            .addAnnotation(Override.class)
            .addModifiers(Modifier.PUBLIC)
            .returns(void.class);
    ClassName manager = ClassName.get(PACKAGE_NAME, CLASS_NAME_MANAGER);
    // register activity.
    for (Element element : elements) {
      // the target of this annotation can only be a type.
      TypeElement activity = (TypeElement) element;
      String path = activity.getAnnotation(Router.class).value();
      registering.addStatement("$T.$L($S, $T.class)", manager, METHOD_NAME_REGISTER, path, activity);
    }
    // create class.
    ClassName register = ClassName.get(PACKAGE_NAME, CLASS_NAME_REGISTER);
    TypeSpec moduleRegister = TypeSpec.classBuilder(moduleRegisterName(moduleName))
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addSuperinterface(register)
            .addMethod(registering.build())
            .build();
    // create java source file.
    JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, moduleRegister).build();
    try {
      javaFile.writeTo(filer);
    } catch (IOException e) {
      messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
    }
    return true;
  }
}